/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "ctrl.h"
#include "pspAll.h"
#include "svcReminder.h"

#define MAX_DESC	50

typedef struct TEditAnniv {
	MAnnivDate *mda;
	BOOLEAN		ReminderEnabled;
} EDITANNIV, *LPEDITANNIV;

class DateCtrl
{
	HWND	_hwndDlg;
	HWND	_hwndDate;
	HWND	_hBtnMenu;
	HWND	_hBtnEdit;
	HWND	_hBtnAdd;
	HWND	_hBtnDel;

	BOOLEAN		_ReminderEnabled;

	MAnnivDate	**_pDates;
	WORD		 _numDates;
	WORD		 _curDate;

	BOOLEAN		ItemValid( WORD wIndex ) const;
	BOOLEAN		CurrentItemValid() const;

public:
	DateCtrl( HWND hDlg );
	~DateCtrl();

	MAnnivDate*		Current()				{ return CurrentItemValid() ? _pDates[_curDate] : NULL; };
	WORD			CurrentIndex() const	{ return _curDate; };
	WORD			NumDates() const		{ return _numDates; };
	BOOLEAN			ReminderEnabled() const	{ return _ReminderEnabled; };

	MAnnivDate*		FindDateById( const WORD wId );

	VOID EnableCurrentItem();
	VOID EnableReminderCtrl( BOOLEAN bEnabled );

	INT	 SetCurSel( WORD wIndex );

	INT AddDate( MAnnivDate &mda );
	INT DeleteDate( WORD wIndex );

	INT DBGetBirthDay( HANDLE hContact, LPCSTR szBaseProto );
	INT DBWriteBirthDay( HANDLE hContact );

	INT DBGetAnniversaries( HANDLE hContact );
	INT DBWriteAnniversaries( HANDLE hContact );

	// notification handlers
	VOID OnMenuPopup();
	VOID OnCurrentItemDateChanged( LPNMDATETIMECHANGE lpChange );
	VOID OnRemindEditChanged();
	VOID OnReminderChecked();
};

/**
 * name:	DateCtrl
 * class:	DateCtrl
 * desc:	constructor
 * param:	none
 * return:	nothing
 **/
DateCtrl::DateCtrl( HWND hDlg )
{
	_hwndDlg = hDlg;
	_hBtnAdd = GetDlgItem( hDlg, BTN_ADD );
	_hBtnDel = GetDlgItem( hDlg, BTN_DELETE );
	_hBtnEdit = GetDlgItem( hDlg, BTN_EDIT );
	_hBtnMenu = GetDlgItem( hDlg, BTN_MENU );
	_hwndDate = GetDlgItem( hDlg, EDIT_ANNIVERSARY_DATE );
	_ReminderEnabled = DB::Setting::GetByte( SET_REMIND_ENABLED, DEFVAL_REMIND_ENABLED );

	_pDates = NULL;
	_curDate = 0;
	_numDates = 0;
	
	// set button tooltips
	SendMessage( _hBtnAdd, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Add a new anniversary" ), MBF_TCHAR );
	SendMessage( _hBtnDel, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Delete an existing anniversary" ), MBF_TCHAR );

	// limit textinput
	SendDlgItemMessage( _hwndDlg, EDIT_REMIND, EM_LIMITTEXT, 2, 0 );
	SendDlgItemMessage( _hwndDlg, SPIN_REMIND, UDM_SETRANGE32, 0, 50 );

	SetUserData( hDlg, this );
	
	// birthday is shown as an item in any case
	{
		MAnnivDate mdb;

		mdb.Id( ANID_BIRTHDAY );
		mdb.Description( TranslateT( "Birthday") );
		AddDate( mdb );
	}
}

/**
 * name:	DateCtrl
 * class:	DateCtrl
 * desc:	destructor
 * param:	none
 * return:	nothing
 **/
DateCtrl::~DateCtrl()
{
	WORD i;

	if( _pDates != NULL ) {
		for( i = 0; i < _numDates; i++ ) {
			delete _pDates[i];
		}
		free( _pDates );
	}
}

/**
 * name:	DateCtrl
 * class:	ItemValid
 * desc:	tests whether the item pointed to by the wIndex is valid or not
 * param:	wIndex	- index to desired item
 * return:	TRUE if item is valid, FALSE otherwise
 **/
BOOLEAN DateCtrl::ItemValid( WORD wIndex ) const
{
	return ( _pDates != NULL && wIndex < _numDates && _pDates[wIndex] != NULL );
}

/**
 * name:	DateCtrl
 * class:	CurrentItemValid
 * desc:	checks, whether currently selected item is valid
 * param:	none
 * return:	TRUE if item is valid, FALSE otherwise
 **/
BOOLEAN DateCtrl::CurrentItemValid() const
{
	return ItemValid( _curDate );
}

/**
 * name:	DateCtrl
 * class:	EnableReminderCtrl
 * desc:	enables or disables reminder controls
 * param:	none
 * return:	TRUE if item is valid, FALSE otherwise
 **/
VOID DateCtrl::EnableReminderCtrl( BOOLEAN bEnabled )
{
	bEnabled &= _ReminderEnabled != NServices::Reminder::REMIND_OFF;
	EnableWindow( GetDlgItem( _hwndDlg, CHECK_REMIND ), bEnabled );
	EnableWindow( GetDlgItem( _hwndDlg, EDIT_REMIND ), bEnabled );
	EnableWindow( GetDlgItem( _hwndDlg, SPIN_REMIND ), bEnabled );
	EnableWindow( GetDlgItem( _hwndDlg, TXT_REMIND ), bEnabled );
}

/**
 * name:	EnableCurrentItem
 * class:	DateCtrl
 * desc:	make control readonly if required
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::EnableCurrentItem()
{
	MAnnivDate *pCurrent = Current();

	if( pCurrent ) {
		HANDLE hContact;
	
		PSGetContact( _hwndDlg, hContact );

		const BOOLEAN bEnabled
			= !hContact ||
			  ( pCurrent->Flags() & CTRLF_HASCUSTOM ) || 
			  !( pCurrent->Flags() & ( CTRLF_HASPROTO|CTRLF_HASMETA ) ) ||
			  !DB::Setting::GetByte( SET_PROPSHEET_PCBIREADONLY, 0 );

		EnableWindow( _hBtnEdit, bEnabled );
		EnableWindow( _hBtnDel, bEnabled );
		EnableWindow( _hwndDate, bEnabled );
	}
}

/**
 * name:	FindDateById
 * class:	DateCtrl
 * desc:	returns an iterator to an item with the given id
 * param:	wId		- id the returned item must have
 * return:	if an date with the wId was found - iterator to item,
 *			NULL otherwise
 **/
MAnnivDate* DateCtrl::FindDateById( const WORD wId )
{
	WORD i;

	if( _pDates != NULL ) {
		for( i = 0; i < _numDates; i++ ) {
			if( _pDates[i]->Id() < ANID_NONE && _pDates[i]->Id() == wId ) {
				return _pDates[i];
			}
		}
	}
	return NULL;
}

/**
 * name:	AddDate
 * class:	DateCtrl
 * desc:	Add a new item to the array of dates
 * param:	mda		- the date to add
 * return:	0 on success, -1 on failure, 1 if the item to change was edited before and the new item was not set
 **/
INT DateCtrl::AddDate( MAnnivDate &mda )
{
	MAnnivDate *pmda, **pmd;

	// if a date with wID exists, replace it
	if( ( pmda = FindDateById( mda.Id() ) ) != NULL ) {
		BOOLEAN bChanged = pmda->IsChanged(),
			bRemindChanged = pmda->IsReminderChanged();

		if( !bChanged ) {
			pmda->Set( mda );
			pmda->Module( mda.Module() );
			pmda->Description( mda.Description() );
			pmda->Flags( mda.Flags() );
		}
		if( !bRemindChanged ) {
			pmda->RemindOption( mda.RemindOption() );
			pmda->RemindOffset( mda.RemindOffset() );
		}
		return bChanged || bRemindChanged;
	}
	if( mda.Id() == ANID_NONE )
		mda.Id( _numDates - 1 );

	if( pmd = ( MAnnivDate ** )realloc( _pDates, ( _numDates + 1 ) * sizeof( pmda ) ) ) {
		_pDates = pmd;
		if( _pDates[_numDates] = new MAnnivDate( mda ) ) {
			_numDates++;
			return 0;
		}
	}
	return 1;
}

/**
 * name:	DeleteDate
 * class:	DateCtrl
 * desc:	Delete the item on the position identified by wIndex
 * param:	pDateCtrl	- pointer to the date control's data structure
 *			wIndex		- index of the item to delete
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DeleteDate( WORD wIndex )
{
	if( !ItemValid( wIndex ) ) return 1;
	
	// only delete values, but not the item
	if( _pDates[wIndex]->Id() == ANID_BIRTHDAY ) {
		HANDLE	hContact;
		LPCSTR	szBaseProto;

		PSGetContact( _hwndDlg, hContact );
		PSGetBaseProto( _hwndDlg, szBaseProto );

		// protocol value exists?
		if( _pDates[wIndex]->DBGetDate( hContact, szBaseProto, SET_CONTACT_BIRTHDAY, SET_CONTACT_BIRTHMONTH, SET_CONTACT_BIRTHYEAR ) ) {
			_pDates[wIndex]->SetFlags( MAnnivDate::MADF_HASPROTO );
		}
		else {
			_pDates[wIndex]->ZeroDate();
		}

		_pDates[wIndex]->RemindOption( BST_INDETERMINATE );
		_pDates[wIndex]->RemindOffset( ( BYTE )-1 );

		_pDates[wIndex]->RemoveFlags( MAnnivDate::MADF_HASCUSTOM );
		_pDates[wIndex]->SetFlags( MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_REMINDER_CHANGED );
	}
	else {
		delete _pDates[wIndex];
		_numDates--;
		if( wIndex < _numDates )
			memmove( _pDates + wIndex, _pDates + wIndex + 1, ( _numDates - wIndex ) * sizeof( *_pDates ) );
		ZeroMemory( _pDates + _numDates, sizeof( *_pDates ) );
		if( _curDate >= _numDates )
			_curDate = _numDates - 1;
	}
	SendMessage( GetParent( _hwndDlg ), PSM_CHANGED, NULL, NULL );
	SetCurSel( _curDate );
	return 0;
}

/**
 * name:	DateCtrl_DBGetBirthDay
 * desc:
 * param:
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBGetBirthDay( HANDLE hContact, LPCSTR szBaseProto )
{
	MAnnivDate mdb;

	if( !mdb.DBGetBirthDate( hContact, (char *)szBaseProto ) ) {
		mdb.DBGetReminderOpts( hContact );
		return AddDate( mdb ) > 0;
	}
	return 0;
}

/**
 * name:	DateCtrl_DBGetBirthDay
 * desc:
 * param:
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBGetAnniversaries( HANDLE hContact )
{
	MAnnivDate mda;

	WORD i;
	BOOLEAN bChanged = FALSE;

	for( i = 0; i < ANID_LAST && !mda.DBGetAnniversaryDate( hContact, i ); i++ ) {
		mda.DBGetReminderOpts( hContact );
		switch( AddDate( mda ) ) {
			case -1:
				return bChanged;
			case 1:
				bChanged |= 1;
				break;
		}
	}
	return bChanged;
}

/**
 * name:	DBWriteBirthDay
 * class:	DateCtrl
 * desc:	writes the birthday for a contact to database
 * param:	hContact - the contact to write the anniversaries to
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBWriteBirthDay( HANDLE hContact )
{
	MAnnivDate *pmdb;

	if( ( pmdb = FindDateById( ANID_BIRTHDAY ) ) == NULL )
		return 1;
		
	if( pmdb->IsChanged() ) {
		// save birthday, to mBirthday module by default
		if( pmdb->Flags() & pmdb->MADF_HASCUSTOM )
			pmdb->DBWriteBirthDate( hContact );
		else
			pmdb->DBDeleteBirthDate( hContact );
	}

	if( pmdb->IsReminderChanged() ) {
		pmdb->DBWriteReminderOpts( hContact );
	}
	pmdb->RemoveFlags( MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_REMINDER_CHANGED );
	return 0;
}

/**
 * name:	DBWriteAnniversaries
 * class:	DateCtrl
 * desc:	write all anniversaries to the database
 * param:	hContact - the contact to write the anniversaries to
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBWriteAnniversaries( HANDLE hContact )
{
	const LPCSTR szPrefix[] = { "Reminder", "Offset", "Desc", "Day", "Month", "Year", "Stamp", "Date" };
	CHAR szSet0[MAXSETTING];
	WORD i, ret, ofs, wIndex = 0;

	for( i = 0; i < _numDates; i++ ) {
		if(
			_pDates[i] != NULL &&
			!_pDates[i]->DBWriteAnniversaryDate( hContact, wIndex ) &&
			!_pDates[i]->DBWriteReminderOpts( hContact )
		  )
		{
			wIndex++;
		}
	}
	// delete reluctant items
	do {
		ofs = mir_snprintfA( szSet0, SIZEOF( szSet0 ), "Anniv%d", wIndex );
		ret = 1;
		for( i = 0; i < SIZEOF( szPrefix ); i++ ) {
			mir_strncpy( szSet0 + ofs, szPrefix[i], SIZEOF( szSet0 ) - ofs );
			ret &= DB::Setting::Delete( hContact, USERINFO, szSet0 );
		}
	}
	while( wIndex++ <= ANID_LAST && !ret );
	return 0;
}

/**
 * name:	SetCurSel
 * class:	DateCtrl
 * desc:	shows the item, identified by wIndex
 * param:	pDateCtrl	- pointer to the date control's data structure
 *			wIndex		- index of the item to delete
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::SetCurSel( WORD wIndex )
{
	BOOLEAN bEnabled = ItemValid( wIndex );

	EnableWindow( _hwndDate, bEnabled );
	EnableWindow( _hBtnEdit, bEnabled );
	EnableWindow( _hBtnDel, bEnabled && _pDates[wIndex]->IsValid() );
	if( !bEnabled ) {
		EnableReminderCtrl( FALSE );
		return 1;
	}
	_curDate = wIndex;

	// set date of date control
	if( _pDates[wIndex]->IsValid() ) {
		SYSTEMTIME st = _pDates[wIndex]->SystemTime();
		DateTime_SetSystemtime( _hwndDate, GDT_VALID, &st );
		DateTime_SetFormat( _hwndDate, NULL );
	}
	else {
		TCHAR szText[MAX_DESC];
		mir_snprintfT( szText, MAX_DESC, _T( "'%s'" ), TranslateT( "Unspecified") );
		DateTime_SetSystemtime( _hwndDate, GDT_NONE, NULL );
		DateTime_SetFormat( _hwndDate, szText );
	}
	// set edit button's caption
	SetWindowText( _hBtnEdit, _pDates[wIndex]->Description() );

	// set reminder options
	CheckDlgButton( _hwndDlg, CHECK_REMIND, _pDates[wIndex]->RemindOption() );
	OnReminderChecked();
	EnableCurrentItem();
	return 0;
}

/**
 * name:	OnMenuPopup
 * class:	DateCtrl
 * desc:	is called to show a popup menu for all anniversaries of a contact
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnMenuPopup()
{
	POINT pt = { 0, 0 };
	RECT rc;
	MENUITEMINFO mii;
	HMENU hMenu;
	WORD i;

	if( hMenu = CreatePopupMenu() ) {
		SetFocus( _hBtnMenu );

		ZeroMemory( &mii, sizeof( MENUITEMINFO ) );
		mii.cbSize = sizeof( MENUITEMINFO );
		mii.fMask = MIIM_ID|MIIM_STRING|MIIM_STATE;

		// insert the items
		for( i = 0; i < _numDates; i++ ) {
			mii.fState = _pDates[i]->IsValid() ? MFS_CHECKED : MFS_UNCHECKED;
			mii.dwTypeData = ( LPTSTR )_pDates[i]->Description();
			mii.wID = WM_USER + i;
			if( !InsertMenuItem( hMenu, i, TRUE, &mii ) ) {
				DestroyMenu( hMenu );
				return;
			}
		}
		ClientToScreen( _hBtnMenu, &pt );
		GetClientRect( _hBtnMenu, &rc );
		i = TrackPopupMenuEx( hMenu, TPM_RIGHTALIGN|TPM_RETURNCMD, pt.x + rc.right, pt.y + rc.bottom, _hwndDlg, NULL );
		DestroyMenu( hMenu );
		SendMessage( _hBtnMenu, BM_SETCHECK, NULL, NULL );
		if( i >= WM_USER ) SetCurSel( i - WM_USER );
	}
}

/**
 * name:	OnMenuPopup
 * class:	DateCtrl
 * desc:	is called to show a popup menu for all anniversaries of a contact
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnCurrentItemDateChanged( LPNMDATETIMECHANGE lpChange )
{
	MAnnivDate *pCurrent = Current();

	if( pCurrent && !pCurrent->IsEqual( lpChange->st ) ) {
		HWND hPs = GetParent( _hwndDlg );

		// save the new date to the structure
		DateTime_SetFormat( _hwndDate, NULL );
		pCurrent->Set( lpChange->st, TRUE );
		pCurrent->SetFlags( MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_HASCUSTOM );
		
		// notify parent of the change
		SendMessage( hPs, PSM_CHANGED, NULL, NULL );
		EnableWindow( _hBtnDel, TRUE );

		// update the age and zodiac controls on the general propertysheetpage
		if( pCurrent->Id() == ANID_BIRTHDAY ) {
			HWND hwndGeneral;
			// update the control
			if( SendMessage( hPs, PSM_GETPAGEHWND, IDD_CONTACT_GENERAL, ( LPARAM )ghInst ) && ( hwndGeneral = ( HWND )GetWindowLongPtr( hPs, DWL_MSGRESULT ) ) ) {
				SendMessage( hwndGeneral, PSPM_SETAGEANDZIDIAC, NULL, ( LPARAM )pCurrent );
			}
		}
	}
}

/**
 * name:	OnRemindEditChanged
 * class:	DateCtrl
 * desc:	is called, if reminder edit control was changed
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnRemindEditChanged()
{
	MAnnivDate	*pCurrent = Current();

	if( pCurrent ) {
		UINT iVal = GetDlgItemInt( _hwndDlg, EDIT_REMIND, NULL, FALSE );

		if( iVal != pCurrent->RemindOffset() && IsDlgButtonChecked( _hwndDlg, CHECK_REMIND ) == BST_CHECKED ) {
			SendMessage( GetParent( _hwndDlg ), PSM_CHANGED, NULL, NULL );
			pCurrent->SetFlags( MAnnivDate::MADF_REMINDER_CHANGED );
			pCurrent->RemindOffset( iVal );
		}
	}
}

/**
 * name:	OnReminderChecked
 * class:	DateCtrl
 * desc:	is called if reminder checkbox's state was changed
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnReminderChecked()
{
	HANDLE hContact;
	LPCSTR szBaseProto;
	INT state;
	TCHAR buf[6];
	MAnnivDate *pCurrent = Current();

	PSGetContact( _hwndDlg, hContact );
	if( !hContact || !PSGetBaseProto( _hwndDlg, szBaseProto ) || !pCurrent ) {
		EnableReminderCtrl( FALSE );
		return;
	}
	if( pCurrent->RemindOption() != ( state = IsDlgButtonChecked( _hwndDlg, CHECK_REMIND ) ) ) {
		pCurrent->RemindOption( state );
		if( !PspIsLocked( _hwndDlg ) ) {
			pCurrent->SetFlags( MAnnivDate::MADF_REMINDER_CHANGED );
			SendMessage( GetParent( _hwndDlg ), PSM_CHANGED, NULL, NULL );
		}
	}
	switch( state ) {
		case BST_CHECKED:
			if( pCurrent->RemindOffset() == ( BYTE )-1 )
				_itot( DB::Setting::GetByte( SET_REMIND_OFFSET, DEFVAL_REMIND_OFFSET ), buf, 10 );
			else
				_itot( pCurrent->RemindOffset(), buf, 10 );
			EnableWindow( GetDlgItem( _hwndDlg, EDIT_REMIND ), _ReminderEnabled );
			EnableWindow( GetDlgItem( _hwndDlg, SPIN_REMIND ), _ReminderEnabled );
			break;
		case BST_UNCHECKED:
			*buf = 0;
			EnableWindow( GetDlgItem( _hwndDlg, EDIT_REMIND ), FALSE );
			EnableWindow( GetDlgItem( _hwndDlg, SPIN_REMIND ), FALSE );
			break;
		case BST_INDETERMINATE:
			_itot( DB::Setting::GetByte( SET_REMIND_OFFSET, DEFVAL_REMIND_OFFSET ), buf, 10 );
			EnableWindow( GetDlgItem( _hwndDlg, EDIT_REMIND ), FALSE );
			EnableWindow( GetDlgItem( _hwndDlg, SPIN_REMIND ), FALSE );
			break;
	}
	SetDlgItemText( _hwndDlg, EDIT_REMIND, buf );
}

/**
 * name:	DlgProc_AnniversaryEditor
 * desc:
 * param:
 * return:	0 on success 1 otherwise
 **/
INT CALLBACK DlgProc_AnniversaryEditor( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	switch( uMsg ) {
		case WM_INITDIALOG:
		{
			SYSTEMTIME st;
			LPEDITANNIV editAnniv = ( LPEDITANNIV )lParam;

			if( !PtrIsValid( editAnniv ) || !editAnniv->mda ) break;
			SetUserData( hDlg, lParam );

			if( !editAnniv->mda->Description() || editAnniv->mda->IsValid() )
				SetWindowText( hDlg, LPGENT( "Edit Anniversary") );

			// set icons
			{
				const NIcoLib::ICONCTRL idIcon[] = {
					{ ICO_DLG_ANNIVERSARY,	WM_SETICON,		NULL		},
					{ ICO_DLG_ANNIVERSARY,	STM_SETIMAGE,	ICO_DLGLOGO	},
					{ ICO_BTN_OK,			BM_SETIMAGE,	IDOK		},
					{ ICO_BTN_CANCEL,		BM_SETIMAGE,	IDCANCEL	}
				};
				const INT numIconsToSet = DB::Setting::GetByte( SET_ICONS_BUTTONS, 1 ) ? SIZEOF( idIcon ) : 2;
				
				NIcoLib::SetCtrlIcons( hDlg, idIcon, numIconsToSet );
			}

			// translate controls
			SendDlgItemMessage( hDlg, IDOK, BUTTONTRANSLATE, NULL, NULL );
			SendDlgItemMessage( hDlg, IDCANCEL, BUTTONTRANSLATE, NULL, NULL );
			TranslateDialogDefault( hDlg );

			// init controls
			EnableWindow( GetDlgItem( hDlg, EDIT_CATEGORY ), editAnniv->mda->Id() != ANID_BIRTHDAY );
			SetDlgItemText( hDlg, EDIT_CATEGORY, editAnniv->mda->Description() );
			st = editAnniv->mda->SystemTime();
			MonthCal_SetCurSel( GetDlgItem( hDlg, EDIT_ANNIVERSARY_DATE ), &st );
			EnableWindow( GetDlgItem( hDlg, CHECK_REMIND ), editAnniv->ReminderEnabled );
			CheckDlgButton( hDlg, CHECK_REMIND, editAnniv->mda->RemindOffset() );
			return TRUE;
		}

		case WM_CTLCOLORSTATIC:
			switch( GetDlgCtrlID( ( HWND )lParam ) ) {
				case STATIC_WHITERECT:
				case ICO_DLGLOGO:
					SetBkColor( ( HDC )wParam, RGB( 255, 255, 255 ) );
					return ( BOOL )GetStockObject( WHITE_BRUSH );
			}
			break;

		case WM_COMMAND:

			switch( LOWORD( wParam ) ) {
				case EDIT_CATEGORY:
					if( HIWORD( wParam ) == EN_UPDATE ) {
						EnableWindow( GetDlgItem( hDlg, IDOK ), GetWindowTextLength( ( HWND )lParam ) > 0 );
					}
					break;

				case IDOK:
				{
					LPEDITANNIV editAnniv = ( LPEDITANNIV )GetUserData( hDlg );

					// read new description
					{
						HWND hEdit = GetDlgItem( hDlg, EDIT_CATEGORY );
						INT len = Edit_GetTextLength( hEdit );
						LPTSTR pszText;

						if(
							len == 0 ||
							( pszText = ( LPTSTR )_alloca( ( len + 1 ) * sizeof( TCHAR ) ) ) == NULL ||
							!Edit_GetText( hEdit, pszText, len + 1 )
						  )
						{
							MsgErr( hDlg, LPGENT( "Please enter a valid Description first!") );
							break;
						}

						if( _tcsicmp( pszText, editAnniv->mda->Description() ) ) {
							editAnniv->mda->Description( pszText );
							editAnniv->mda->SetFlags( MAnnivDate::MADF_HASCUSTOM|MAnnivDate::MADF_CHANGED );
						}
					}
					
					// get new date
					{
						SYSTEMTIME st;
						MonthCal_GetCurSel( GetDlgItem( hDlg, EDIT_ANNIVERSARY_DATE ), &st );
						if( !editAnniv->mda->IsEqual( st ) ) {
							editAnniv->mda->SetDate( st );
							editAnniv->mda->SetFlags( MAnnivDate::MADF_HASCUSTOM|MAnnivDate::MADF_CHANGED );
						}
					}

					// get new reminder option
					{
						BYTE state;
						if( ( state = IsDlgButtonChecked( hDlg, CHECK_REMIND ) ) != editAnniv->mda->RemindOption() ) {
							editAnniv->mda->SetFlags( MAnnivDate::MADF_REMINDER_CHANGED );
							editAnniv->mda->RemindOption( state );
						}
					}
				}
				case IDCANCEL:
					return EndDialog( hDlg, LOWORD( wParam ) );
			}
			break;
	}
	return FALSE;
}

/**
 * name:	DlgProc_pspOrigin
 * desc:	dialog procedure for the advanced contact information propertysheetpage
 * param:	hDlg	- handle to the dialog window
 *			uMsg	- the message to handle
 *			wParam	- parameter
 *			lParam	- parameter
 * return:	different values
 **/
INT CALLBACK DlgProc_pspAnniversary( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	DateCtrl* pDateCtrl = ( DateCtrl* )GetUserData( hDlg );

	switch( uMsg ) {
		
		case WM_INITDIALOG:
		{
			// create and initiate the data structure
			if( !( pDateCtrl = new DateCtrl( hDlg ) ) )
				return FALSE;
			// hContact == NULL or reminder disabled
			pDateCtrl->EnableReminderCtrl( lParam != NULL );

			SendDlgItemMessage( hDlg, EDIT_AGE, EM_LIMITTEXT, 3, 0 );
			SendDlgItemMessage( hDlg, SPIN_AGE, UDM_SETRANGE32, 0, 200 );

			TranslateDialogDefault( hDlg );
			Ctrl_InitTextColours();	
			return TRUE;
		}

		case WM_CTLCOLOREDIT:
			if( ( HWND )lParam && DB::Setting::GetByte( SET_PROPSHEET_SHOWCOLOURS, TRUE ) ) {
				LPCTRL pCtrl = ( LPCTRL )GetUserData( ( HWND )lParam );
				if( PtrIsValid( pCtrl ) ) return Ctrl_SetTextColour( ( HDC )wParam, pCtrl->wFlags );
			}
			return FALSE;

		case WM_CTLCOLORSTATIC:
			/*
			switch( GetWindowLong( ( HWND )lParam, GWL_ID ) ) {
				case EDIT_ANNIVERSARY_DATE:
				case EDIT_AGE:
				case TXT_NAME:
				case TITLE_AGE:
				case TITLE_ZODIAC:
				case TEXT_ZODIAC:
				case FRAME_AGE:
					return ( BOOL )GetSysColor( COLOR_WINDOW );
			}
			*/
			SetBkColor( ( HDC )wParam, RGB( 255, 255, 255 ) );
			return ( BOOL )GetStockObject( WHITE_BRUSH );
			/*
			SetTextColor( ( HDC )wParam, GetSysColor( COLOR_WINDOWTEXT ) );
			SetBkColor( ( HDC )wParam, GetSysColor( COLOR_WINDOW ) );
			return GetSysColor( COLOR_WINDOW );
			*/

		case WM_NOTIFY:
			switch( (( LPNMHDR )lParam )->idFrom ) {
				case 0:
				{
					HANDLE hContact = ( HANDLE )( ( LPPSHNOTIFY )lParam )->lParam;
					LPCSTR szBaseProto;
					
					switch( (( LPNMHDR )lParam )->code ) {
						case PSN_INFOCHANGED:
						{
							BYTE bChanged = 0;

							if( !PSGetBaseProto( hDlg, szBaseProto ) || *szBaseProto == 0 ) break;

							//
							// birthday groupbox
							//
							if( PtrIsValid( pDateCtrl ) ) {
								bChanged |= pDateCtrl->DBGetBirthDay( hContact, szBaseProto );
								bChanged |= pDateCtrl->DBGetAnniversaries( hContact );
								pDateCtrl->SetCurSel( 0 );
							}

							// read age an zodiac
							{
								MAnnivDate mtb;
								HWND hPs = GetParent( hDlg );
								DLGCOMMAND dCmd;

								dCmd.hInst = ghInst;
								dCmd.idDlg = IDD_CONTACT_ANNIVERSARY;
								dCmd.idDlgItem = 0;
								dCmd.uMsg = PSPM_GETBIRTHDATE;
								dCmd.wParam = NULL;
								dCmd.lParam = ( LPARAM )&mtb;

								if(
									SendMessage( hPs, PSM_DLGMESSAGE, NULL, ( LPARAM )&dCmd ) ||
									!mtb.DBGetBirthDate( hContact, (LPSTR)szBaseProto )
								  )
								{
									DlgProc_pspAnniversary( hDlg, PSPM_SETAGEANDZIDIAC, NULL, ( LPARAM )&mtb );
									Edit_Init( hContact, GetDlgItem( hDlg, EDIT_AGE ) );
								}
								else
									bChanged |= Edit_SetValue( hDlg, EDIT_AGE, hContact, USERINFO, szBaseProto, SET_CONTACT_AGE );
							}

							SetWindowLongPtr( hDlg, DWL_MSGRESULT, bChanged ? PSP_CHANGED : 0 );
							break;
						}
						
						case PSN_APPLY:
						{
							BYTE state = ( BYTE )SendDlgItemMessage( hDlg, CHECK_REMIND, BM_GETCHECK, NULL, NULL );

							if( !PSGetBaseProto( hDlg, szBaseProto ) || *szBaseProto == 0 ) break;

							// save anniversaries
							if( PtrIsValid( pDateCtrl ) ) {
								pDateCtrl->DBWriteBirthDay( hContact );
								pDateCtrl->DBWriteAnniversaries( hContact );
							}
							Edit_WriteValue( hDlg, EDIT_AGE, hContact, USERINFO, szBaseProto, SET_CONTACT_AGE, DBVT_WORD );
							break;
						}
						case PSN_ICONCHANGED:
						{
							const NIcoLib::ICONCTRL idIcon[] = {
								{ ICO_COMMON_BIRTHDAY,	STM_SETIMAGE,	ICO_BIRTHDAY },
								{ ICO_BTN_ADD,			BM_SETIMAGE,	BTN_ADD		 },
								{ ICO_BTN_DELETE,		BM_SETIMAGE,	BTN_DELETE	 }
							};

							NIcoLib::SetCtrlIcons( hDlg, idIcon, SIZEOF( idIcon ) );
							break;
						}
					}
					break; /* case 0 */
				}

				case EDIT_ANNIVERSARY_DATE:
					if( !PspIsLocked( hDlg ) && PtrIsValid( pDateCtrl ) ) {
						LPNMHDR lpNmhdr = ( LPNMHDR )lParam;
						
						switch( lpNmhdr->code ) {
							case DTN_DATETIMECHANGE:
								pDateCtrl->OnCurrentItemDateChanged( ( LPNMDATETIMECHANGE ) lParam );
								break;
							case DTN_DROPDOWN:
							{
								HWND hMonthCal = DateTime_GetMonthCal( lpNmhdr->hwndFrom );
								SetWindowLong( hMonthCal, GWL_STYLE, GetWindowLong( hMonthCal, GWL_STYLE )|MCS_WEEKNUMBERS );
								break;
							}
						}
					}
					break;
			} /* switch( (( LPNMHDR )lParam )->idFrom ) */
			break;

		case WM_COMMAND:
			switch( LOWORD( wParam ) ) {
				case EDIT_REMIND:
					if( !PspIsLocked( hDlg ) && HIWORD( wParam ) == EN_UPDATE )
						pDateCtrl->OnRemindEditChanged();
					break;

				/**
				 * name:	BTN_MENU
				 * desc:	the button to dropdown the list to show all items is pressed
				 **/
				case BTN_MENU:
					if( HIWORD( wParam ) == BN_CLICKED )
						pDateCtrl->OnMenuPopup();
					break;

				/**
				 * name:	BTN_ADD
				 * desc:	creates a new dialog to add a new anniversary
				 **/
				case BTN_ADD:
					if( HIWORD( wParam ) == BN_CLICKED && PtrIsValid( pDateCtrl ) ) {
						MAnnivDate Date;
						EDITANNIV  editAnniv;
						
						editAnniv.mda = &Date;
						editAnniv.ReminderEnabled = pDateCtrl->ReminderEnabled();

						if( IDOK == DialogBoxParam( ghInst, MAKEINTRESOURCE( IDD_ANNIVERSARY_EDITOR ), hDlg, DlgProc_AnniversaryEditor, ( LPARAM )&editAnniv ) ) { 
							SendMessage( GetParent( hDlg ), PSM_CHANGED, NULL, NULL );
							if( !pDateCtrl->AddDate( Date ) )
								pDateCtrl->SetCurSel( pDateCtrl->NumDates() - 1 );
						}
					}
					break;

				/**
				 * name:	BTN_EDIT
				 * desc:	edit the currently selected anniversary
				 **/
				case BTN_EDIT:
					if( HIWORD( wParam ) == BN_CLICKED && PtrIsValid( pDateCtrl ) ) {
						EDITANNIV editAnniv;

						if( ( editAnniv.mda = pDateCtrl->Current() ) == NULL ) {
							MsgErr( hDlg, LPGENT( "No valid date selected for editing!") );
							break;	
						}
						editAnniv.ReminderEnabled = pDateCtrl->ReminderEnabled();
						if(
							IDOK == DialogBoxParam( ghInst, MAKEINTRESOURCE( IDD_ANNIVERSARY_EDITOR ), hDlg, DlgProc_AnniversaryEditor, ( LPARAM )&editAnniv )&&
							( editAnniv.mda->Flags() & ( MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_REMINDER_CHANGED ) )
						  )
						{ 
							SendMessage( GetParent( hDlg ), PSM_CHANGED, NULL, NULL );
							pDateCtrl->SetCurSel( pDateCtrl->CurrentIndex() );
						}
					}
					break;

				/**
				 * name:	BTN_DELETE
				 * desc:	user wants to delete an anniversary
				 **/
				case BTN_DELETE:
				{
					if( HIWORD( wParam ) == BN_CLICKED && PtrIsValid( pDateCtrl ) )
					{
						MAnnivDate *pCurrent;

					  pCurrent = pDateCtrl->Current();
						if( pCurrent )
						{
							INT rc = MsgBox( hDlg, MB_YESNO|MB_ICON_QUESTION|MB_NOPOPUP, LPGENT( "Delete" ), NULL, 
								LPGENT( "Do you really want to delete the %s?" ),	pCurrent->Description() );
						
							if( rc == IDYES )
							{
								pDateCtrl->DeleteDate( pDateCtrl->CurrentIndex() );
							}
						}
					}
					break;
				}

				/**
				 * name:	CHECK_REMIND
				 * desc:	state of reminder checkbox is changed
				 **/
				case CHECK_REMIND:
					if( PtrIsValid( pDateCtrl ) && HIWORD( wParam ) == BN_CLICKED )
						pDateCtrl->OnReminderChecked();
					break;

				case EDIT_AGE:
					if( !PspIsLocked( hDlg ) && HIWORD( wParam ) == EN_UPDATE )
						Edit_Changed( hDlg, LOWORD( wParam ), USERINFO, SET_CONTACT_AGE );
					break;
			} /* switch( LOWORD( wParam ) ) */
			break;

		case PSPM_SETAGEANDZIDIAC:
		{
			MAnnivDate *mt = ( MAnnivDate* )lParam;
			INT age;
			MZodiac zod;

			if( !PtrIsValid( mt ) ) break;

			zod = mt->Zodiac();
			if( zod.pszName != NULL ) {
				ShowWindow( GetDlgItem( hDlg, TEXT_ZODIAC ), SW_SHOW );
				SetDlgItemText( hDlg, TEXT_ZODIAC, TranslateTS( zod.pszName ) );
				SendDlgItemMessage( hDlg, IDC_ZODIAC, STM_SETIMAGE, IMAGE_ICON, ( LPARAM )zod.hIcon );
			}
			else
				ShowWindow( GetDlgItem( hDlg, TEXT_ZODIAC ), SW_HIDE );
			
			if( ( age = mt->Age() ) > 0 )
				SetDlgItemInt( hDlg, EDIT_AGE, age , FALSE );
			break;
		}

		case PSPM_GETBIRTHDATE:
		{
			MAnnivDate *pbdFind, *pbd = ( MAnnivDate* )lParam;

			if( !PtrIsValid( pbd ) || !PtrIsValid( pDateCtrl ) ) break;

			if( ( pbdFind = pDateCtrl->FindDateById( ANID_BIRTHDAY ) ) != NULL ) {
				pbd->SetDate( *pbdFind );
				return TRUE;
			}
			break;
		}
		case WM_DESTROY:
			Edit_Destroy( hDlg, EDIT_AGE );
			delete pDateCtrl;
			break;
	}
	return FALSE;
}
